﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace System.Peppers.Win32
{
    /// <summary>
    /// Class for mapping network drives programmatically
    /// </summary>
    public sealed class NetworkDrive
    {
        private const int MORE_DATA = 234,
            BUFFER_SIZE = 1024;

        /// <summary>
        /// Maps a network drive with specified parameters
        /// </summary>
        /// <param name="driveParams">Settings for mapped drive</param>
        public void Map(NetworkDriveParameters driveParams)
        {
            NETRESOURCE resource = new NETRESOURCE
                {
                    dwScope = RESOURCESCOPE.RESOURCE_GLOBALNET,
                    dwType = RESOURCETYPE.RESOURCETYPE_DISK,
                    dwDisplayType = RESOURCEDISPLAYTYPE.RESOURCEDISPLAYTYPE_SHARE,
                    lpLocalName = ConvertLetter(driveParams.DriveLetter),
                    lpRemoteName = driveParams.RemotePath,
                };
            WNetFlags flags = WNetFlags.NONE;

            if (driveParams.Temporary)
            {
                flags |= WNetFlags.CONNECT_TEMPORARY | WNetFlags.CONNECT_UPDATE_RECENT;
            }

            int error = Mpr.WNetAddConnection2(resource, driveParams.Password, driveParams.Username, flags);
            if (error != (int)HRESULT.S_OK)
            {
                throw new Win32Exception(error);
            }
        }

        /// <summary>
        /// Unmaps a drive, without forcing closed connections
        /// </summary>
        /// <param name="driveLetter">Letter of the drive to unmap</param>
        public void Unmap(char driveLetter)
        {
            Unmap(driveLetter, false);
        }

        /// <summary>
        /// Unmaps a drive, forcing it if passed a true
        /// </summary>
        /// <param name="driveLetter">Letter of the drive to unmap</param>
        /// <param name="force">Pass a true to force close existing connections</param>
        public void Unmap(char driveLetter, bool force)
        {
            int error = Mpr.WNetCancelConnection2(ConvertLetter(driveLetter), WNetFlags.NONE, force);
            if (error != (int)HRESULT.S_OK)
            {
                throw new Win32Exception(error);
            }
        }

        /// <summary>
        /// Returns the network path of the specified network drive
        /// </summary>
        /// <param name="driveLetter">Drive letter to check</param>
        /// <returns>Path to the shared network folder</returns>
        public string GetNetworkPath(char driveLetter)
        {
            StringBuilder builder = new StringBuilder(BUFFER_SIZE);
            int length = BUFFER_SIZE;
            int error = Mpr.WNetGetConnection(ConvertLetter(driveLetter), builder, ref length);
            if (error != (int)HRESULT.S_OK)
            {
                throw new Win32Exception(error);
            }
            return builder.ToString();
        }

        /// <summary>
        /// Returns the username (including domain) used for the specified network drive
        /// </summary>
        /// <param name="driveLetter">Drive letter to check</param>
        /// <returns>Username of the mapped drive</returns>
        public string GetUsername(char driveLetter)
        {
            StringBuilder builder = new StringBuilder(BUFFER_SIZE);
            int length = BUFFER_SIZE;
            int error = Mpr.WNetGetUser(ConvertLetter(driveLetter), builder, ref length);
            if (error != (int)HRESULT.S_OK)
            {
                throw new Win32Exception(error);
            }
            return builder.ToString();
        }

        /// <summary>
        /// Enumerates mapped drives
        /// </summary>
        /// <returns>Information about mapped drives</returns>
        public IEnumerable<NetworkDriveInfo> EnumerateMappings()
        {
            int error = 0,
                entries = 0,
                buffer = 16384;
            IntPtr hEnum = IntPtr.Zero,
                ptrBuffer = Marshal.AllocHGlobal(buffer);
            try
            {
                error = Mpr.WNetOpenEnum(RESOURCESCOPE.RESOURCE_CONNECTED, RESOURCETYPE.RESOURCETYPE_DISK, RESOURCEUSAGE.DEFAULT, null, out hEnum);
                if (error != (int)HRESULT.S_OK)
                {
                    throw new Win32Exception(error);
                }

                NETRESOURCE resource = null;
                do
                {
                    entries = -1;
                    buffer = 16384;
                    error = Mpr.WNetEnumResource(hEnum, ref entries, ptrBuffer, ref buffer);
                    if (error != (int)HRESULT.S_OK && error != MORE_DATA)
                    {
                        throw new Win32Exception(error);
                    }

                    int ptr = ptrBuffer.ToInt32();
                    for (int i = 0; i < entries; i++)
                    {
                        resource = (NETRESOURCE)Marshal.PtrToStructure(new IntPtr(ptr), typeof(NETRESOURCE));
                        ptr += Marshal.SizeOf(resource);
                        yield return new NetworkDriveInfo(resource);
                    }
                }
                while (error == MORE_DATA);
            }
            finally
            {
                Marshal.FreeHGlobal(ptrBuffer);
                if (hEnum != IntPtr.Zero)
                {
                    error = Mpr.WNetCloseEnum(hEnum);
                    if (error != (int)HRESULT.S_OK)
                    {
                        throw new Win32Exception(error);
                    }
                }
            }
        }

        private string ConvertLetter(char driveLetter)
        {
            return driveLetter + ":";
        }
    }
}
